package com.sk.ccflower.lib.util;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.sk.ccflower.lib.util.json.JSONCompatible;
import com.sk.ccflower.lib.util.json.JSONWrapper;

public class JSONUtil {
	
	private static final Log log = LogFactory.getLog(JSONUtil.class);
	
	public static String toJSON(Object o) {
		StringWriter out = new StringWriter();
		try {
			writeJSON(o, out);
		} catch (IOException e) { }
		return out.toString();
	}
	
	public static String escapeJSONString(String input) {
		StringWriter out = new StringWriter();
		try {
			escapeJSONString(input, out);
		} catch (IOException e) { }
		return out.toString();
	}

	/**
	 * original source from commons-lang package
	 * @param str
	 * @param out
	 * @throws IOException
	 * @see {@link StringEscapeUtils#escapeJavaScript(String)}
	 */
	public static void escapeJSONString(String str, Writer out) throws IOException {
		if (out == null) {
			throw new IllegalArgumentException("The Writer must not be null");
		}
		if (str == null) {
			return;
		}
		int sz;
		sz = str.length();
		for (int i = 0; i < sz; i++) {
			char ch = str.charAt(i);

			// handle unicode
			if (ch > 0xfff) {
				out.write("\\u" + hex(ch));
			} else if (ch > 0xff) {
				out.write("\\u0" + hex(ch));
			} else if (ch > 0x7f) {
				out.write("\\u00" + hex(ch));
			} else if (ch < 32) {
				switch (ch) {
				case '\b':
					out.write('\\');
					out.write('b');
					break;
				case '\n':
					out.write('\\');
					out.write('n');
					break;
				case '\t':
					out.write('\\');
					out.write('t');
					break;
				case '\f':
					out.write('\\');
					out.write('f');
					break;
				case '\r':
					out.write('\\');
					out.write('r');
					break;
				default:
					if (ch > 0xf) {
						out.write("\\u00" + hex(ch));
					} else {
						out.write("\\u000" + hex(ch));
					}
					break;
				}
			} else {
				switch (ch) {
				case '"':
					out.write('\\');
					out.write('"');
					break;
				case '\\':
					out.write('\\');
					out.write('\\');
					break;
				case '/':
					out.write('\\');
					out.write('/');
					break;
				default:
					out.write(ch);
					break;
				}
			}
		}
	}

    /**
     * <p>Returns an upper case hexadecimal <code>String</code> for the given
     * character.</p>
     * 
     * @param ch The character to convert.
     * @return An upper case hexadecimal <code>String</code>
     */
    private static String hex(char ch) {
        return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
    }

	@SuppressWarnings("rawtypes")
	public static void writeJSON(Object o, Writer out) throws IOException {
		if (o == null) {
			out.write("null");
		}
		else if (o instanceof JSONCompatible) {
			((JSONCompatible)o).writeJSON(out);
		}
		else if (o instanceof Boolean) {
			writeJSON((Boolean)o, out);
		}
		else if (o instanceof Number) {
			writeJSON((Number)o, out);
		}
		else if (o instanceof String) {
			writeJSON((String)o, out);
		}
		else if (o instanceof URL) {
			writeJSON(o.toString(), out);
		}
		else if (o instanceof Date) {
			writeJSON(((Date)o).getTime(), out);
		}
		else if (o.getClass().isArray()) {
			writeJSONArray(o, out);
		}
		else if (o instanceof Map) {
			writeJSON((Map)o, out);
		}
		else if (o instanceof Collection) {
			writeJSON((Collection)o, out);
		}
		else {
			JSONWrapper wrapper = JSONWrapper.getInstance(o);
//			if (log.isDebugEnabled()) {
//				log.debug(o.getClass() + ": wrapper class is " + wrapper.getClass());
//			}
			wrapper.writeJSON(out);
		}
	}

	public static <T> void writeJSONArray(Object array, Writer out) throws IOException {
		if (array == null) {
			out.write("null");
			return;
		}

		out.write("[");
		int length = Array.getLength(array);
		for (int i = 0; i < length; i++) {
			Object value = Array.get(array, i);
			writeJSON(value, out);
			if (i < length - 1)
				out.write(",");
		}
		out.write("]");
	}

	public static void writeJSON(Boolean value, Writer out) throws IOException {
		out.write((value)? "true": "false");
	}

	@SuppressWarnings("rawtypes")
	public static void writeJSON(Collection collection, Writer out) throws IOException {
		if (collection == null) {
			out.write("null");
			return;
		}

		out.write("[");
		Iterator iterator = collection.iterator();
		while (iterator.hasNext()) {
			Object value = iterator.next();
			writeJSON(value, out);
			if (iterator.hasNext())
				out.write(",");
		}
		out.write("]");
	}

	@SuppressWarnings("rawtypes")
	public static void writeJSON(Map map, Writer out) throws IOException {
		if (map == null) {
			out.write("null");
			return;
		}

		out.write("{");
		Iterator keys = map.keySet().iterator();
		while (keys.hasNext()) {
			Object key = keys.next();
			out.write("\"");
			out.write(StringEscapeUtils.escapeJavaScript(key.toString()));
			out.write("\":");

			Object value = map.get(key);
			writeJSON(value, out);
			if (keys.hasNext())
				out.write(",");
		}
		out.write("}");
	}

	public static void writeJSON(Number value, Writer out) throws IOException {
		out.write((value == null)? "null": value.toString());
	}

	public static void writeJSON(String value, Writer out) throws IOException {
		if (value == null) {
			out.write("null");
			return;
		}

		out.write("\"");
		escapeJSONString(value, out);
		out.write("\"");
	}

	public static Object fromJSON(String json) {
		// TODO
		throw new UnsupportedOperationException();
	}
	
	/**
	 * json 문자열에서 key값에 해당하는 value 값을 리턴한다.
	 * 
	 * @see http://code.google.com/p/json-simple/wiki/DecodingExamples
	 * 
	 * 2011.04.14 윤승환(willrain@nate.com) 추가
	 * 
	 * @param jsonText json 문자열  (ex : "{\"\first":123, \"second\":[{}]}" 
	 * @param key 찾으려는 문자열
	 * @return
	 */
	/*
	public static String keyFind(String jsonText, String key) {
		Object rtObject = null;
		JSONParser parser = new JSONParser();
		KeyFinder finder = new KeyFinder();
		finder.setMatchKey(key);
		try {
			while(!finder.isEnd()) {
				parser.parse(jsonText, finder, true);
			
				if (finder.isFound()) {
					finder.setFound(false);
					rtObject = finder.getValue();
				}
			}
		}
		catch (ParseException pe) {
			log.fatal(pe.toString());
		}
		
		return String.valueOf(rtObject);
		
	}
	*/
}
